// SphereWorld.cpp
// OpenGL SuperBible
// New and improved (performance) sphere world
// Program by Richard S. Wright Jr.

#include <GLTools.h>
#include <GLShaderManager.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLFrame.h>
#include <GLMatrixStack.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>
#include <GL\freeglut.h>
#include <iostream>
#include <math.h>
#include <stdio.h>
#include "glsl.h"
#include <GL\glew.h>
//#include "Cart.h"

#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif

GLShaderManager		SM;			// Shader Manager
GLShaderManager		shaderManager;
GLMatrixStack		modelViewMatrix;		// Modelview Matrix
GLMatrixStack		projectionMatrix;		// Projection Matrix
GLFrustum			viewFrustum;			// View Frustum
GLGeometryTransform	transformPipeline;		// Geometry Transform Pipeline

GLFrame             cameraFrame; 
GLTriangleBatch		torusBatch;

float track_height=20.0f;		// Height of the track
float track_innerradius=101.0f;// Inner radius of the track
float track_outerradius=99.0f;// Outer radius of the track
int nh = 4; //Number of Hills

int n;              // number of hills
int H;             //Hill height
float g;        //  % gravity constant
int R;            //% radius
float time;
float totalstep;    //% totalstep
float angle;
float angle2 = 1.04;
float angle3 = 1.08;
float angle_v;
float angle_a;
float k_1;
float k_2;
float k_3;
float k_4;

float tx;
float ty;
float tz;
float tx2;
float ty2;
float tz2;
float dx = 0;
float dy = 0;
float dz = 0;
float h = 0.1;
float dFrameDuration;

float initialspeed = 0.1;

double winWidth = 800;
double winHeight = 600;
double aspect	= 1;

bool madeTrees = false;
bool car = true;
bool car2 = false;
bool car3 = false;
float rndX[14];
float rndZ[14];

bool camera = true;
bool camera2 = false;
bool camera3 = false;
bool camera4 = false;

/////////////////
float equation(float pAngleV, float pAngle)
{
	n = nh;
	H = 20;
	g = 9.807; 
	R = 100;
	float angle_a;
	angle_a = ((n*n*n)*(H*H)*sin(n*pAngle)*(pAngle*pAngle)-n*g*H)*cos(n*pAngle)/((R*R)+cos(n*pAngle)*cos(n*pAngle)*((n*n)*(H*H)));

	return angle_a;
};

void rk4(float tAngle){
	
	k_1 = equation(angle_v,tAngle);
    k_2 = equation(angle_v+0.5*k_1*h,tAngle);
    k_3 = equation(angle_v+0.5*k_2*h,tAngle);
    k_4 = equation(angle_v+k_3*h,tAngle);
	angle_a = (1/6)*(k_1+2*k_2+2*k_3+k_4);
    angle_v = angle_v + angle_a*h;
    tAngle = tAngle+ angle_v*h;

	dx = (R*cos(tAngle)) - tx;
    dy = (0.5*H*cos(n*tAngle)) - ty;
    dz = (R*sin(tAngle)) - tz;

	tx = R*cos(tAngle);
    tz = R*sin(tAngle);
    ty= 0.5*H*cos(n*tAngle);

	if(car)
	angle = angle+ angle_v*h;
	if(car2)
	angle2 = angle2+ angle_v*h;
	if(car3)
	angle3 = angle3+ angle_v*h;

	std::cout << ty << "\n";
}

void cart(){
	glNewList(5, GL_COMPILE);

		glBegin(GL_QUADS);									// Draw A Quad
			glColor3f(0.0f,1.0f,0.0f);						// Set The Color To Green
			glVertex3f( 2.0f, 2.0f,-2.0f);					// Top Right Of The Quad (Top)
			glVertex3f(-2.0f, 2.0f,-2.0f);					// Top Left Of The Quad (Top)
			glVertex3f(-2.0f, 2.0f, 2.0f);					// Bottom Left Of The Quad (Top)
			glVertex3f( 2.0f, 2.0f, 2.0f);					// Bottom Right Of The Quad (Top)
			glColor3f(1.0f,0.5f,0.0f);						// Set The Color To Orange
			glVertex3f( 2.0f,-2.0f, 2.0f);					// Top Right Of The Quad (Bottom)
			glVertex3f(-2.0f,-2.0f, 2.0f);					// Top Left Of The Quad (Bottom)
			glVertex3f(-2.0f,-2.0f,-2.0f);					// Bottom Left Of The Quad (Bottom)
			glVertex3f( 2.0f,-2.0f,-2.0f);					// Bottom Right Of The Quad (Bottom)
			glColor3f(1.0f,0.5f,0.5f);						// Set The Color To Red
			glVertex3f( 2.0f, 2.0f, 2.0f);					// Top Right Of The Quad (Front)
			glVertex3f(-2.0f, 2.0f, 2.0f);					// Top Left Of The Quad (Front)
			glVertex3f(-2.0f,-2.0f, 2.0f);					// Bottom Left Of The Quad (Front)
			glVertex3f( 2.0f,-2.0f, 2.0f);					// Bottom Right Of The Quad (Front)
			glColor3f(1.0f,1.0f,0.0f);						// Set The Color To Yellow
			glVertex3f( 2.0f,-2.0f,-2.0f);					// Top Right Of The Quad (Back)
			glVertex3f(-2.0f,-2.0f,-2.0f);					// Top Left Of The Quad (Back)
			glVertex3f(-2.0f, 2.0f,-2.0f);					// Bottom Left Of The Quad (Back)
			glVertex3f( 2.0f, 2.0f,-2.0f);					// Bottom Right Of The Quad (Back)
			glColor3f(0.0f,0.0f,1.0f);						// Set The Color To Blue
			glVertex3f(-2.0f, 2.0f, 2.0f);					// Top Right Of The Quad (Left)
			glVertex3f(-2.0f, 2.0f,-2.0f);					// Top Left Of The Quad (Left)
			glVertex3f(-2.0f,-2.0f,-2.0f);					// Bottom Left Of The Quad (Left)
			glVertex3f(-2.0f,-2.0f, 2.0f);					// Bottom Right Of The Quad (Left)
			glColor3f(1.0f,0.0f,1.0f);						// Set The Color To Violet
			glVertex3f( 2.0f, 2.0f,-2.0f);					// Top Right Of The Quad (Right)
			glVertex3f( 2.0f, 2.0f, 2.0f);					// Top Left Of The Quad (Right)
			glVertex3f( 2.0f,-2.0f, 2.0f);					// Bottom Left Of The Quad (Right)
			glVertex3f( 2.0f,-2.0f,-2.0f);					// Bottom Right Of The Quad (Right)
		glEnd();											// Done Drawing The Quad

	glEndList();
}

void makeFloor()
{
	glBegin(GL_QUADS);
		glVertex3f(200,0,200);
		glVertex3f(-200,0,200);
		glVertex3f(-200,0,-200);
		glVertex3f(200,0,-200);
	glEnd();

}

void makeTrees()
{

		for(int i = 0; i < 15; i++)
		{
			if(!madeTrees)
			{
				for(int i = 0; i < 15; i++)
				{	
				rndX[i] = rand() % 400;
				if(rndX[i] > 200)
					rndX[i] = 200 - rndX[i];

				rndZ[i] = rand() % 400;
				if(rndZ[i] > 200)
					rndZ[i] = 200 - rndZ[i];
				madeTrees = true;
				}
			}

			glPushMatrix();
			glTranslatef(rndX[i], 0, rndZ[i]); 
			glColor3f(0.184314,0.309804,0.184314);
			glutSolidCube(20);
			glPushMatrix();
			glTranslatef(0,15,0);
			glutSolidCube(10);
			glPopMatrix();
			glPopMatrix();
		}
		madeTrees = true;
}
        
//////////////////////////////////////////////////////////////////
// This function does any needed initialization on the rendering
// context. 
void SetupRC()
    {
	// Initialze Shader Manager
	shaderManager.InitializeStockShaders();
	
	glEnable(GL_DEPTH_TEST);

	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
 
	glClearColor(0.2f, 0.2f, 0.2f, 1.0f);

	angle = 2;
	angle_v = initialspeed;

	glNewList(2, GL_COMPILE); 
		glColor3f(1,0, 0);  
		glutSolidCube(7);
	glEndList();

	glNewList(3, GL_COMPILE); 
		glColor3f(0.82,0.82, 0.82);  
		glutSolidSphere(0.1,20,10); 
	glEndList();

	

	glNewList(4, GL_COMPILE);
	double x=-3.14;

	for (x; x<3.14;x= x+.005)
	{
		//Track
		glColor3f(1.0,1.0, 1.0);
		glPushMatrix();
		k_1 = equation(angle_v,x);
		k_2 = equation(angle_v+0.5*k_1*h,x);
		k_3 = equation(angle_v+0.5*k_2*h,x);
		k_4 = equation(angle_v+k_3*h,x);

		tx = R*cos(x);
		tz = R*sin(x);
		ty= 0.5*H*cos(n*x);
		glTranslatef(tx, ty, tz);
		//glTranslatef(track_innerradius*cos(x),track_height*sin(nh*x),track_innerradius*sin(x));
		glCallList(2);
		glPopMatrix();

	angle_a = (1/6)*(k_1+2*k_2+2*k_3+k_4);
    angle_v = angle_v + angle_a*h;
    angle = angle+ angle_v*h;

	}
	glEndList();

	angle = 1;
	angle_v = initialspeed;

    }

void CameraSettings()
{
	if(camera)
	{
	  glMatrixMode(GL_PROJECTION);
	  glLoadIdentity();
	  gluPerspective(40.0, aspect, 1.00, 0.0);
	  // Perspective Parms( Angle, Aspect Ratio, Front Clipping, Back Clipping)
	  
	  gluLookAt(0.0, 75, 275.0,  0.0, 0.0, 0,  0.0, 1.0, 0); 
	  //gluLookAt(0.0, 300.0, 5.0,  0.0, 0.0, 0,  0.0, 1.0, 0); 
	  //gluLookAt(0.0, 2.5, 3.0, 0.0, -0.5, -1.0, 0.0, 1.0, 0.0);
	  // LookAt Parms (Eye_X, Eye_Y, Eye_Z, View_X, View_Y, View_Z, Up_X, Up_Y, UpZ)

	  glMatrixMode(GL_MODELVIEW);
	}

	if(camera2)
	{
		glMatrixMode(GL_PROJECTION);
		  glLoadIdentity();
		  gluPerspective(40.0, aspect, 1.00, 0.0);
		  // Perspective Parms( Angle, Aspect Ratio, Front Clipping, Back Clipping)
	  
		  gluLookAt(-1, 0, -1,  0.0, 0.0, 0,  0.0, 1.0, 0); 
		  //gluLookAt(0.0, 300.0, 5.0,  0.0, 0.0, 0,  0.0, 1.0, 0); 
		  //gluLookAt(0.0, 2.5, 3.0, 0.0, -0.5, -1.0, 0.0, 1.0, 0.0);
		  // LookAt Parms (Eye_X, Eye_Y, Eye_Z, View_X, View_Y, View_Z, Up_X, Up_Y, UpZ)

		  glTranslatef(-tx, -ty - 5, -tz);
		  glMatrixMode(GL_MODELVIEW);

		  glLoadIdentity();
	}

	if(camera3)
	{
		glMatrixMode(GL_PROJECTION);
		  glLoadIdentity();
		  gluPerspective(40.0, aspect, 1.00, 0.0);
		  // Perspective Parms( Angle, Aspect Ratio, Front Clipping, Back Clipping)
	  
		  //gluLookAt(-1, 0, -1,  0.0, 0.0, 0,  0.0, 1.0, 0); 
		  gluLookAt(0.0, 300.0, 5.0,  0.0, 0.0, 0,  0.0, 1.0, 0); 
		  //gluLookAt(0.0, 2.5, 3.0, 0.0, -0.5, -1.0, 0.0, 1.0, 0.0);
		  // LookAt Parms (Eye_X, Eye_Y, Eye_Z, View_X, View_Y, View_Z, Up_X, Up_Y, UpZ)

		  glMatrixMode(GL_MODELVIEW);

		  glLoadIdentity();
	}

	if(camera4)
	{
		glMatrixMode(GL_PROJECTION);
		  glLoadIdentity();
		  gluPerspective(40.0, aspect, 1.00, 0.0);
		  // Perspective Parms( Angle, Aspect Ratio, Front Clipping, Back Clipping)
	
		  gluLookAt(100.0, 100.0, 100.0,  0.0, 0.0, 0,  0.0, 1.0, 0); 
		  // LookAt Parms (Eye_X, Eye_Y, Eye_Z, View_X, View_Y, View_Z, Up_X, Up_Y, UpZ)

		  glMatrixMode(GL_MODELVIEW);

		  glLoadIdentity();
	}


}

///////////////////////////////////////////////////
// Screen changes size or is initialized
void ChangeSize(int nWidth, int nHeight)
{
	winWidth = nWidth;
	winHeight = nHeight;
	aspect = (float)nWidth/(float)nHeight;

	  glViewport(0, 0, (GLsizei)nWidth, (GLsizei)nHeight);
	  glMatrixMode(GL_PROJECTION);
	  glLoadIdentity();
	  gluPerspective(40.0, aspect, 1.00, 0.0);
	  // Perspective Parms( Angle, Aspect Ratio, Front Clipping, Back Clipping)
	  
	  gluLookAt(0.0, 75, 275.0,  0.0, 0.0, 0,  0.0, 1.0, 0); 
	  //gluLookAt(0.0, 300.0, 5.0,  0.0, 0.0, 0,  0.0, 1.0, 0); 
	  //gluLookAt(0.0, 2.5, 3.0, 0.0, -0.5, -1.0, 0.0, 1.0, 0.0);
	  // LookAt Parms (Eye_X, Eye_Y, Eye_Z, View_X, View_Y, View_Z, Up_X, Up_Y, UpZ)

	  glMatrixMode(GL_MODELVIEW);

	  glLoadIdentity();
    }

        
// Called to draw scene
void RenderScene(void)
	{

	static double supa; // This value will be remembered each time display is called
	supa+=.005; // Increment 5 degrees each call
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		
	glColor3f(1.0,1.0, 1.0);  
	glPushMatrix();
	
	cart();
	
	//shader = SM.loadfromFile("vertexshader.txt","fragmentshader.txt");
	//Renders the Carts	
	rk4(angle);
	glTranslatef(tx, ty, tz);
	glRotatef(angle, dz, dy, dx);
		glCallList(5);
	glPopMatrix();
	car = false;
	car2 = true;

	rk4(angle2);
	glPushMatrix();
	glTranslatef(tx,ty,tz);
		glCallList(5);
	glPopMatrix(); 
	car2 = false;
	car3 = true;

	rk4(angle3);
	glPushMatrix();
	glTranslatef(tx,ty,tz);
		glCallList(5);
	glPopMatrix();  
	car3 = false;
	car = true;

	//Rendesr the track
	glCallList(4);

	//Renders the Trees
	makeTrees();

	//Renders the floor
	glColor3f(0.0,1.0,0.0);
	makeFloor();

	CameraSettings();

	glFlush();
    // Do the buffer Swap
    glutSwapBuffers();
        
    // Tell GLUT to do it again
    glutPostRedisplay();
    }




// Respond to arrow keys by moving the camera frame of reference
void SpecialKeys(int key, int x, int y)
    {
	float linear = 0.1f;
	float angular = float(m3dDegToRad(5.0f));
	
	if(key == GLUT_KEY_UP)
		//glTranslatef(-linear,0,0);
		cameraFrame.MoveForward(linear);
	
	if(key == GLUT_KEY_DOWN)
		cameraFrame.MoveForward(-linear);
	
	if(key == GLUT_KEY_LEFT)
		cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);
	
	if(key == GLUT_KEY_RIGHT)
		cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);

    }

void keyboard (unsigned char key, int x, int y)
{
    switch (key)
    {
        
	case 'a':			// Move forward slowly
		
		glTranslated(0.0,0.0, 1);
		glutPostRedisplay();
		break;
	case 'A':			// Move forward more quickly
		glTranslated(0.0,0.0, 2);
		glutPostRedisplay();
		break;
	case 'z':			// Move backward slowly
		glTranslated(0.0,0.0, -1);
		glutPostRedisplay();
		break;
	case 'Z':			// Move backward more quickly
		glTranslated(0.0,0.0, -2);
		glutPostRedisplay();
		break;
	case '.':	// Rotate scene around origin clockwise
		glRotated(5.0, 0.0, 1.0, 0.0);
		glutPostRedisplay();
		break;
	case ',': // Rotate scene around origin anticlockwise
		glRotated(5.0, 0.0, -1.0, 0.0);
		glutPostRedisplay();
		break;
	case '1':
		camera = true;
		camera2 = false;
		camera3 = false;
		camera4 = false;
		break;
	case '2':
		camera = false;
		camera2 = true;
		camera3 = false;
		camera4 = false;
		break;
	case '3':
		camera = false;
		camera2 = false;
		camera3 = true;
		camera4 = false;
		break;
	case '4':
		camera = false;
		camera2 = false;
		camera3 = false;
		camera4 = true;
		break;


    case 27:
    case 'q':
    case 'Q':
            exit (1);
    break;
    }
}

int main(int argc, char* argv[])
    {
	gltSetWorkingDirectory(argv[0]);
		
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(winWidth, winHeight);
  
    glutCreateWindow("RollerCoaster Simulation");

	glutKeyboardFunc(keyboard);
    glutSpecialFunc(SpecialKeys);
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
    
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
        }
        

    SetupRC();
	glutDisplayFunc(RenderScene);
    glutMainLoop();    
    return 0;
    }

	//glTranslatef(((track_innerradius+track_outerradius)/2)*cos(supa),track_height*sin(nh*supa)+2, ((track_innerradius+track_outerradius)/2)*sin(supa));
       //glutSolidSphere(0.5,20,10);